/*  
	------------------------------------------------------------------------
	 NQ Core Library
	
	 Homepage: http://www.awzhome.de/
	------------------------------------------------------------------------
	
	This Source Code Form is subject to the terms of the Mozilla Public
	License, v. 2.0. If a copy of the MPL was not distributed with this
	file, You can obtain one at http://mozilla.org/MPL/2.0/.
   
	The Original Code is code of NQ Core Library.

	The Initial Developer of the Original Code is Andreas Weizel.
	Portions created by the Initial Developer are
	Copyright (C) 2012 Andreas Weizel. All Rights Reserved.
	
	Contributor(s): (none)
	
	------------------------------------------------------------------------
*/

using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using System.Windows.Forms;
using System.Security.Cryptography;
using System.Text;
using System.Linq;
using AWZhome.NQ.Core.Utility;

namespace AWZhome.NQ.Core
{
	/// <summary>
	/// NQ's full featured implementation of
	/// <see cref="AWZhome.NQ.Core.INQServiceManager" /> interface.
	/// </summary>
	/// <typeparam name="SC">Service creator class, derived from <see cref="AWZhome.NQ.Core.INQServiceInstanceCreator"/> interface.</typeparam>
	public class NQServiceManagerImpl : INQServiceManager
	{
		private NQHostMode _hostMode = NQHostMode.General;
		private bool _isActive = false;
		private string _componentPath = null;
		private bool _loadingSingletons = false;
		private object _loadedSingleton = null;
		private bool _singletonCtorLock = false;

		private INQComponentLoader _componentLoader = null;

		private Dictionary<Type, Type> _serviceList = new Dictionary<Type, Type>();
		private Dictionary<Type, object> _serviceSinglesList = new Dictionary<Type, object>();
		private Dictionary<string, IList<Type>> _attachLists = new Dictionary<string, IList<Type>>();
		private Dictionary<Type, Type> _substitutions = new Dictionary<Type, Type>();
		private Dictionary<string, INQComponentInfo> _componentInfos = new Dictionary<string, INQComponentInfo>();
		private Dictionary<Type, INQServiceInfo> _serviceInfos = new Dictionary<Type, INQServiceInfo>();
		private Dictionary<Type, INQServiceInstanceCreator> _serviceCreators = new Dictionary<Type, INQServiceInstanceCreator>();
		private List<string> _loadedAssemblies = new List<string>();

		private string _logFile;
		private bool _noLog = false;
		private NQLogType _logTypesEvents = NQLogType.Error | NQLogType.Warning;
		private NQLogType _logTypesToFile = NQLogType.Error | NQLogType.Warning;
		private LogMessageCache _logMessageCache = null;

		private StreamWriter _logWriter;

		private CommandLineHelper _commandLineHelper = null;

		private object _syncObject = null;
		private Control _guiSyncObject = null;

		public event NQServiceCreatedEventHandler ServiceCreated;
		public event NQServiceDestroyedEventHandler ServiceDestroyed;
		public event NQLogEventHandler MessageLogged;
		public event EventHandler<NQCancelEventArgs> AppExitRequested;

		public NQServiceManagerImpl(string[] cmdargs, NQHostMode hostMode)
			: this(cmdargs, hostMode, null)
		{
		}

		public NQServiceManagerImpl(string[] cmdargs, NQHostMode hostMode, INQComponentLoader componentLoader)
		{
			_logWriter = null;

			// The "general" host type is not accepted
			if (hostMode == NQHostMode.General)
			{
				throw new ArgumentException("No specific host mode has been set.");
			}

			this._hostMode = hostMode;

			_logMessageCache = new LogMessageCache();

			// Read command-line parameters
			string cfgFileName = null;

			// The "pf" parameter (parameter file name) must be treated here
			if ((cmdargs.Length > 1) && (cmdargs[0] == "-pf"))
			{
				cfgFileName = cmdargs[1];
			}
			else
			{
				// No parameter file name given, use the defaults
				switch (this._hostMode)
				{
					case NQHostMode.Console:
						cfgFileName = "nqcparam.config";
						break;
					case NQHostMode.WindowsService:
						cfgFileName = "nqsparam.config";
						break;
					case NQHostMode.WebServer:
						cfgFileName = "nqwebparam.config";
						break;
					default:
						cfgFileName = "nqparam.config";
						break;
				}
			}

			_commandLineHelper = new CommandLineHelper(cmdargs, cfgFileName);

			// Create synchronization object for the service manager
			_syncObject = new object();
			if (hostMode == NQHostMode.GUI)
			{
				_guiSyncObject = new Control();
				_guiSyncObject.CreateControl();
				_guiSyncObject.Visible = false;
			}

			// Log configuration
			if (!_commandLineHelper.IsCmdLineParamSet("ln"))
			{
				// Configuration from command-line parameters
				if (_commandLineHelper.IsCmdLineParamSet("li"))
				{
					_logTypesEvents |= NQLogType.Info;
				}
				if (_commandLineHelper.IsCmdLineParamSet("ld"))
				{
					_logTypesEvents |= NQLogType.Debug;
				}
				if (_commandLineHelper.IsCmdLineParamSet("lw"))
				{
					_logTypesEvents |= NQLogType.Warning;
				}
				if (_commandLineHelper.IsCmdLineParamSet("Li"))
				{
					_logTypesToFile |= NQLogType.Info;
				}
				if (_commandLineHelper.IsCmdLineParamSet("Ld"))
				{
					_logTypesToFile |= NQLogType.Debug;
				}
				if (_commandLineHelper.IsCmdLineParamSet("Lw"))
				{
					_logTypesToFile |= NQLogType.Warning;
				}
				string[] lfSetting = _commandLineHelper.IsCmdLineParamSet("lf") ? _commandLineHelper["lf"] : null;
				if ((lfSetting != null) && (lfSetting.Length > 0))
				{
					_logFile = lfSetting[0];
				}
				else
				{
					_logFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
						"NQLog." + GetMD5Hash(Environment.CurrentDirectory) + ".txt");
				}

				// Logging is active, open the log file
				if (!_commandLineHelper.IsCmdLineParamSet("lni"))
				{
					try
					{
						bool firstWrite = !File.Exists(_logFile);
						_logWriter = new StreamWriter(_logFile, true);
						// If the file has been just created, write an introductional line
						if (firstWrite)
						{
							_logWriter.WriteLine("NQ Application Log File: " + this.ComponentPath);
							_logWriter.Flush();
						}
					}
					catch (Exception)
					{
						if (_logWriter != null)
						{
							_logWriter.Close();
							_logWriter = null;
						}
					}
				}
			}
			else
			{
				_noLog = true;
			}

			if (componentLoader == null)
			{
				// Set the component loader
				string[] clSetting = this.GetCmdLineParam("cl");
				if ((clSetting != null) && (clSetting.Length > 0))
				{
					try
					{
						object tempLoader = Activator.CreateInstance(Type.GetType(clSetting[0], true));
						if (tempLoader != null)
						{
							_componentLoader = tempLoader as INQComponentLoader;
							if (_componentLoader == null)
							{
								// Log the creation error
								this.LogMessage(NQLogType.Info, "The component loader \"" + clSetting[0] + "\" does not support the required interface. Default component loader will be used instead.");
							}
						}
					}
					catch (Exception)
					{
						// Log the creation error
						this.LogMessage(NQLogType.Info, "The component loader \"" + clSetting[0] + "\" could not be created. Default component loader will be used instead.");
					}
				}

				if (_componentLoader == null)
				{
					// Use the default component loader
					_componentLoader = new NQAttributeBasedComponentLoader();
				}
			}
			else
			{
				_componentLoader = componentLoader;
			}
		}

		~NQServiceManagerImpl()
		{
			// Close the log file, if necessary
			if (_logWriter != null)
			{
				try
				{
					_logWriter.Close();
					_logWriter = null;
				}
				catch (Exception)
				{
					// We don't need to do anything here
				}
			}
		}

		/// <summary>
		/// Loads component assemblies from the specified directory.
		/// </summary>
		/// <remarks>
		/// The method follows the restrictions given by the command-line parameters "-ca" and "-cf", so
		/// it's not guaranteed that all assemblies will be loaded.
		/// </remarks>
		public void LoadComponents()
		{
			IEnumerable<INQComponentInfo> loaderCompInfos = _componentLoader.LoadComponentData();

			if (loaderCompInfos != null)
			{
				foreach (INQComponentInfo compInfo in loaderCompInfos)
				{
					if (this.CheckCategory(compInfo.Category))
					{
						// Check requirements
						bool requiredComps = true;
						if (compInfo.Requires != null)
						{
							foreach (INQComponentRequirement reqItem in compInfo.Requires)
							{
								// Check if required component is loaded
								requiredComps = requiredComps && _componentInfos.ContainsKey(reqItem.ComponentName);
								if (requiredComps && (reqItem.Version != null))
								{
									bool partcond = NQComparer<Version>.CheckCondition(_componentInfos[reqItem.ComponentName].Version, reqItem.Condition, reqItem.Version);
									if (!partcond)
									{
										// The condition doesn't match the current version, check compatibility conditions
										if (_componentInfos[reqItem.ComponentName].Compatibility != null)
										{
											foreach (INQComponentRequirement compatItem in _componentInfos[reqItem.ComponentName].Compatibility)
											{
												partcond = partcond || NQComparer<Version>.CheckCondition(reqItem.Version, compatItem.Condition, compatItem.Version);
											}
										}
									}

									requiredComps = requiredComps && partcond;
								}
							}
						}

						if (requiredComps || (compInfo.Requires == null))
						{
							// All component requirements have been satisfied, now check NoAutoLoad option
							if (!(compInfo.NoAutoLoad && (this.GetCmdLineParam("ca") == null)))
							{
								// Copy component info to internal list
								_componentInfos.Add(compInfo.Name, compInfo);
								// Component loaders can load the component after the Service Manager gives its "OK"
								if (_componentLoader.LoadComponentAssemblies(compInfo))
								{
									this.LogMessage(NQLogType.Info, "Loaded component " + compInfo.Name);
								}
								else
								{
									this.LogMessage(NQLogType.Error, "Loading assemblies for component " + compInfo.Name + " has failed!");
								}
							}
						}
					}
					else
					{
						// The component's category and the current host mode didn't match, log an info message
						this.LogMessage(NQLogType.Info, "The component " + compInfo.Name + " has not been loaded. It doesn't accept the current host mode.");
					}
				}
			}

			_isActive = true;
		}

		/// <summary>
		/// Scans all loaded assemblies for exported NQ services and loads them.
		/// </summary>
		public void LoadServices()
		{
			LoadServices(null);
		}

		/// <summary>
		/// Scans all loaded assemblies for exported NQ services and loads them.
		/// </summary>
		/// <param name="serviceInstanceCreatorFunc">
		/// Delegate creating instances of custom service instance creators
		/// (implementig <see cref="AWZhome.NQ.Core.INQServiceInstanceCreator"/> interface).
		/// Default service instance creator is used, if delegate is <c>null</c>.
		/// </param>
		public void LoadServices(Func<INQServiceInstanceCreator> serviceInstanceCreatorFunc)
		{
			IEnumerable<INQServiceInfo> loaderServInfos = _componentLoader.LoadServiceData();

			if (loaderServInfos != null)
			{
				foreach (INQServiceInfo servInfo in loaderServInfos)
				{
					if (_componentInfos.ContainsKey(servInfo.ParentComponent))
					{
						// Check the service for component requirements
						bool requiredLoaded = true;
						if (servInfo.Requires != null)
						{
							foreach (INQComponentRequirement reqItem in servInfo.Requires)
							{
								// Check if required component is loaded
								requiredLoaded = requiredLoaded && _componentInfos.ContainsKey(reqItem.ComponentName);
								if (requiredLoaded && (reqItem.Version != null))
								{
									bool partcond = NQComparer<Version>.CheckCondition(_componentInfos[reqItem.ComponentName].Version, reqItem.Condition, reqItem.Version);
									if (!partcond)
									{
										// The condition doesn't match the current version, check compatibility conditions
										if (_componentInfos[reqItem.ComponentName].Compatibility != null)
										{
											foreach (INQComponentRequirement compatItem in _componentInfos[reqItem.ComponentName].Compatibility)
											{
												partcond = partcond || NQComparer<Version>.CheckCondition(reqItem.Version, compatItem.Condition, compatItem.Version);
											}
										}
									}

									requiredLoaded = requiredLoaded && partcond;
								}
							}
						}

						// Only load if the service accepts the environment and belongs to the right category
						if (requiredLoaded && this.CheckCategory(servInfo.ServiceCategory))
						{
							_serviceInfos.Add(servInfo.ServiceInterface, servInfo);
							if (!servInfo.SingleInstance)
							{
								// Multi-instance service -> save type for later creation
								// Single-instance services will be handeled later
								if (!_serviceList.ContainsKey(servInfo.ServiceInterface))
								{
									_serviceList.Add(servInfo.ServiceInterface, servInfo.ServiceType);
								}
							}

							// Save service substitute information
							if (servInfo.Substitutes != null)
							{
								foreach (Type subst in servInfo.Substitutes)
								{
									_substitutions[subst] = servInfo.ServiceInterface;
								}
							}

							// Add to requested attach lists
							if (servInfo.AttachedTo != null)
							{
								foreach (string attach in servInfo.AttachedTo)
								{
									IList<Type> atlist;
									if (!_attachLists.ContainsKey(attach))
									{
										atlist = new List<Type>();
										_attachLists.Add(attach, atlist);
									}
									else
									{
										atlist = _attachLists[attach];
									}
									atlist.Add(servInfo.ServiceInterface);
								}
							}

							// Initialize a ServiceInstanceCreator for the service and cache it for later use
							INQServiceInstanceCreator serviceInstanceCreator = null;
							if (serviceInstanceCreatorFunc != null)
							{
								// Let the delegate create the custom instance
								serviceInstanceCreator = serviceInstanceCreatorFunc();
							}
							else
							{
								// Use default: ServiceInstanceCreator
								serviceInstanceCreator = new ServiceInstanceCreator();
							}

							serviceInstanceCreator.Initialize(servInfo);
							_serviceCreators.Add(servInfo.ServiceInterface, serviceInstanceCreator);
						}
					}
				}
			}

			// Check service consistency
			NQServiceInfoValidator validator = new NQServiceInfoValidator(_serviceInfos, _substitutions, _attachLists);
			NQServiceInfoValidator.FaultyServiceInfo[] faultyServices = validator.CheckServiceConsistency();
			foreach (NQServiceInfoValidator.FaultyServiceInfo faultyService in faultyServices)
			{
				// Throw the exceptions
				this.LogMessage(NQLogType.Error, faultyService.Message);
				throw new NQServiceDefinitionException(faultyService.Reason, faultyService.Message);
			}

			_loadingSingletons = true;

			// Iterate through all single instance ServiceInfos
			foreach (INQServiceInfo singletonServInfo in _serviceInfos.Values)
			{
				if (singletonServInfo.SingleInstance && !_serviceSinglesList.ContainsKey(singletonServInfo.ServiceInterface))
				{
					this.CreateServiceInstanceDirect<object>(singletonServInfo.ServiceInterface, null, null, true);
				}
			}

			_loadingSingletons = false;
			_loadedSingleton = null;

			// Now caching of log messages can be finished, from now on all logging notifications are sent directly
			_logMessageCache.DissolveCache();
			_logMessageCache = null;

			// Invoke post-initialization services from AttachList
			object[] postInitList =
				this.GetAttachedServices<object>(NQCoreAttachLists.ServiceManager_PostServiceInit, true, true);
			if (postInitList != null)
			{
				foreach (object serv in postInitList)
				{
					this.InvokeService(serv);
				}
			}
			// Same with second level
			postInitList =
				this.GetAttachedServices<object>(NQCoreAttachLists.ServiceManager_PostServiceInit2, true, true);
			if (postInitList != null)
			{
				foreach (object serv in postInitList)
				{
					this.InvokeService(serv);
				}
			}
		}

		/// <summary>
		/// Unloads all services and cleans up memory.
		/// </summary>
		public void DestroyServices()
		{
			_isActive = false;

			// Call Dispose() for all single-instance services that support IDisposable
			foreach (var singleService in _serviceSinglesList.Values)
			{
				if (singleService is IDisposable)
				{
					((IDisposable) singleService).Dispose();
				}
			}

			// Clear all service and attach lists
			_serviceSinglesList.Clear();
			_serviceList.Clear();
			_substitutions.Clear();
			_attachLists.Clear();
			_serviceInfos.Clear();
			_componentInfos.Clear();

			if (_componentLoader != null)
			{
				_componentLoader = null;
			}
		}

		#region INQServiceManager members

		public string[] GetCmdLineParam(string paramname)
		{
			if (_commandLineHelper.IsCmdLineParamSet(paramname))
			{
				return _commandLineHelper[paramname];
			}
			else
			{
				return null;
			}
		}

		public bool IsCmdLineParamSet(string paramname)
		{
			return _commandLineHelper.IsCmdLineParamSet(paramname);
		}

		public T CreateService<T>(Type serviceInterface)
		{
			lock (_syncObject)
			{
				return this.CreateServiceInstance<T>(serviceInterface, null, null, false);
			}
		}

		public T CreateDependentService<T, S>(Type serviceInterface, S dependent)
		{
			lock (_syncObject)
			{
				T depService;

				if (typeof(INQDependentService<S>).IsAssignableFrom(typeof(T)))
				{
					depService = (T) this.CreateServiceInstance<INQDependentService<S>>(
						serviceInterface, null, null, false);
					// Initialize dependent service
					((INQDependentService<S>) depService).InitDependent(dependent);
				}
				else
				{
					// As the service does not implement NQ's interfaces, give the parent instance through the constructor
					depService = this.CreateServiceInstance<T>(
						serviceInterface,
						new object[] { dependent },
						new Type[] { typeof(S) },
						false);
				}

				return depService;
			}
		}

		public T CreateDependentService2<T, S1, S2>(Type serviceInterface, S1 dep1, S2 dep2)
		{
			lock (_syncObject)
			{
				T depService;

				if (typeof(INQDependentService2<S1, S2>).IsAssignableFrom(typeof(T)))
				{
					depService = (T) this.CreateServiceInstance<INQDependentService2<S1, S2>>(
						serviceInterface, null, null, false);
					// Initialize dependent service
					((INQDependentService2<S1, S2>) depService).InitDependent(dep1, dep2);
				}
				else
				{
					// As the service does not implement NQ's interfaces, give the parent instances through the constructor
					depService = this.CreateServiceInstance<T>(
						serviceInterface,
						new object[] { dep1, dep2 },
						new Type[] { typeof(S1), typeof(S2) },
						false);
				}

				return depService;
			}
		}

		public T CreateServiceDirect<T>(Type serviceInterface)
		{
			lock (_syncObject)
			{
				return this.CreateServiceInstanceDirect<T>(serviceInterface, null, null, false);
			}
		}

		public T CreateDependentServiceDirect<T, S>(Type serviceInterface, S dependent)
		{
			lock (_syncObject)
			{
				T depService;

				if (typeof(INQDependentService<S>).IsAssignableFrom(typeof(T)))
				{
					depService = (T) this.CreateServiceInstanceDirect<INQDependentService<S>>(
						serviceInterface, null, null, false);
					// Initialize dependent service
					((INQDependentService<S>) depService).InitDependent(dependent);
				}
				else
				{
					// As the service does not implement NQ's interfaces, give the parent instance through the constructor
					depService = this.CreateServiceInstanceDirect<T>(
						serviceInterface,
						new object[] { dependent },
						new Type[] { typeof(S) },
						false);
				}

				return depService;
			}
		}

		public T CreateDependentServiceDirect2<T, S1, S2>(Type serviceInterface, S1 dep1, S2 dep2)
		{
			lock (_syncObject)
			{
				T depService;

				if (typeof(INQDependentService2<S1, S2>).IsAssignableFrom(typeof(T)))
				{
					depService = (T) this.CreateServiceInstanceDirect<INQDependentService2<S1, S2>>(
						serviceInterface, null, null, false);
					// Initialize dependent service
					((INQDependentService2<S1, S2>) depService).InitDependent(dep1, dep2);
				}
				else
				{
					// As the service does not implement NQ's interfaces, give the parent instances through the constructor
					depService = this.CreateServiceInstanceDirect<T>(
						serviceInterface,
						new object[] { dep1, dep2 },
						new Type[] { typeof(S1), typeof(S2) },
						false);
				}

				return depService;
			}
		}

		public T GetService<T>(Type serviceInterface)
		{
			lock (_syncObject)
			{
				Type realserv;
				// Firstly check if this service is substituted by another one
				realserv = this.GetSubstitutingService(serviceInterface);
				if (realserv == null)
				{
					realserv = serviceInterface;
				}

				// Return service object
				if (_serviceSinglesList.ContainsKey(realserv))
				{
					if (!_singletonCtorLock)
					{
						if (!_loadingSingletons || (_serviceSinglesList[realserv] == _loadedSingleton))
						{
							return (T) _serviceSinglesList[realserv];
						}
						else
						{
							// We mustn't access any single-instance service as not all
							// of them are already initialized by now.
							this.LogMessage(NQLogType.Error, "Single-instance services are in initialization phase and are not accessible.");
							throw new NQServiceLoadingException(NQErrorReason.ServiceInitLock, "Single-instance services are in initialization phase and are not accessible.");
						}
					}
					else
					{
						// Single-instance services can't access other single-instance services in their constructors
						this.LogMessage(NQLogType.Error, serviceInterface.Name + ": Single-instance services can't access other single-instance services in their constructors.");
						throw new NQServiceLoadingException(NQErrorReason.ServiceInitLock, serviceInterface.Name + ": Single-instance services can't access other single-instance services in their constructors.");
					}
				}
				else
				{
					this.LogMessage(NQLogType.Error, "Unknown service " + realserv + ".");
					throw new NQServiceLoadingException(NQErrorReason.UnknownService, "Unknown service " + realserv + ".");
				}
			}
		}

		public S[] CreateAttachedServices<S>(string attList)
		{
			return this.GetAttachedServices<S>(attList, true, false);
		}

		public S[] GetAttachedServices<S>(string attList, bool includeMultiInstServices, bool includeSingleInstServices)
		{
			lock (_syncObject)
			{
				if (!_attachLists.ContainsKey(attList))
					return null;

				S[] ret = null;
				List<S> attServices = new List<S>();
				IList<Type> al = _attachLists[attList];

				// Get/create all attached services and return them as an array
				if ((al != null) && (al.Count > 0))
				{
					for (int i = 0; i < al.Count; i++)
					{
						if (this.IsServiceOfType(al[i], typeof(S)))
						{
							// Service is compatible to S
							if (includeMultiInstServices && this.IsServiceCreatable(al[i]))
							{
								// We have a multi-instance service, create a new instance
								attServices.Add(this.CreateServiceInstance<S>(al[i], null, null, false));
							}
							else if (includeSingleInstServices && this.IsServiceRunning(al[i]))
							{
								// We have a single-instance service, get the common instance
								attServices.Add(this.GetService<S>(al[i]));
							}
						}
						else
						{
							// The AttachList contains incompatible services
							this.LogMessage(NQLogType.Error, attList + " contains an incompatible service of type " + _serviceList[al[i]].Name);
							//throw new NQServiceLoadingException(attList + " contains an incompatible service of type " + _serviceList[al[i]].Name);
						}
					}
				}

				ret = new S[attServices.Count];
				attServices.CopyTo(ret);
				return ret;
			}
		}

		public T[] CreateAttachedDepServices<T, S>(string attList, S dependent)
		{

			lock (_syncObject)
			{
				if (!_attachLists.ContainsKey(attList))
					return null;

				T[] ret = null;
				IList<Type> al = _attachLists[attList];
				// Create all attached services and return them as an array
				if ((al != null) && (al.Count > 0))
				{
					ret = new T[al.Count];
					for (int i = 0; i < al.Count; i++)
					{
						ret[i] = this.CreateDependentService<T, S>(al[i], dependent);
					}
				}
				return ret;
			}
		}

		public T[] CreateAttachedDepServices2<T, S1, S2>(string attList, S1 dep1, S2 dep2)
		{
			lock (_syncObject)
			{
				if (!_attachLists.ContainsKey(attList))
					return null;

				T[] ret = null;
				IList<Type> al = _attachLists[attList];
				// Create all attached services and return them as an array
				if ((al != null) && (al.Count > 0))
				{
					ret = new T[al.Count];
					for (int i = 0; i < al.Count; i++)
					{
						ret[i] = this.CreateDependentService2<T, S1, S2>(al[i], dep1, dep2);
					}
				}
				return ret;
			}
		}

		public Type[] GetAttachedServices(string listname)
		{
			lock (_syncObject)
			{
				if (_attachLists.ContainsKey(listname))
				{
					IList<Type> al = _attachLists[listname];
					if (al != null)
					{
						int listcount = al.Count;
						if (listcount > 0)
						{
							Type[] ret = new Type[listcount];
							al.CopyTo(ret, 0);
							return ret;
						}
					}
				}
				return null;
			}
		}

		public string[] GetComponentList()
		{
			lock (_syncObject)
			{
				Dictionary<string, INQComponentInfo>.KeyCollection keys = _componentInfos.Keys;
				if (keys != null)
				{
					int listcount = keys.Count;
					if (listcount > 0)
					{
						string[] ret = new string[listcount];
						keys.CopyTo(ret, 0);
						return ret;
					}
				}
				return null;
			}
		}

		public Type[] GetServiceList()
		{
			lock (_syncObject)
			{
				Dictionary<Type, INQServiceInfo>.KeyCollection keys = _serviceInfos.Keys;
				if (keys != null)
				{
					int listcount = keys.Count;
					if (listcount > 0)
					{
						Type[] ret = new Type[listcount];
						keys.CopyTo(ret, 0);
						return ret;
					}
				}
				return null;
			}
		}

		public string[] GetAllAttachLists()
		{
			lock (_syncObject)
			{
				Dictionary<string, IList<Type>>.KeyCollection keys = _attachLists.Keys;
				if (keys != null)
				{
					int listcount = keys.Count;
					if (listcount > 0)
					{
						string[] ret = new string[listcount];
						keys.CopyTo(ret, 0);
						return ret;
					}
				}
				return null;
			}
		}

		public bool IsServiceRunning(Type serviceInterface)
		{
			lock (_syncObject)
			{
				// Is serviceName an existing single instance service?
				return _serviceSinglesList.ContainsKey(serviceInterface);
			}
		}

		public bool IsServiceCreatable(Type serviceInterface)
		{
			lock (_syncObject)
			{
				// Is serviceName an existing multi instance service?
				return _serviceList.ContainsKey(serviceInterface);
			}
		}

		public bool IsComponentLoaded(string componentName)
		{
			lock (_syncObject)
			{
				// Check if component is existing
				return _componentInfos.ContainsKey(componentName);
			}
		}

		public INQComponentInfo GetComponentInfo(string componentName)
		{
			lock (_syncObject)
			{
				// Return NQComponentInfo object
				if (_componentInfos.ContainsKey(componentName))
				{
					return _componentInfos[componentName];
				}
				else
				{
					return null;
				}
			}
		}

		public INQServiceInfo GetServiceInfo(Type serviceInterface)
		{
			lock (_syncObject)
			{
				// Return NQServiceInfo object
				if (_serviceInfos.ContainsKey(serviceInterface))
				{
					return _serviceInfos[serviceInterface];
				}
				else
				{
					return null;
				}
			}
		}

		public INQServiceInfo GetServiceInfoFromObject(object serviceObject)
		{
			lock (_syncObject)
			{
				if (serviceObject != null)
				{
					try
					{
						// Search for the ServiceInfo with the same type information
						var foundServices = (from s in _serviceInfos.Values
											 where (s.ServiceType == serviceObject.GetType())
											 select s).SingleOrDefault();

						// Simply return foundService, since the LINQ expression returns null, if it doesn't find anything
						return foundServices;
					}
					catch (Exception)
					{
						return null;
					}
				}
				else
				{
					return null;
				}
			}
		}

		public bool IsServiceOfType(Type serviceInterface, System.Type type)
		{
			lock (_syncObject)
			{
				// Compare types
				if (IsServiceCreatable(serviceInterface))
				{
					// Multi-instance service
					return type.IsAssignableFrom(_serviceList[serviceInterface]);
				}
				else if (IsServiceRunning(serviceInterface))
				{
					// Single-instance service
					return type.IsAssignableFrom(_serviceSinglesList[serviceInterface].GetType());
				}
				else
				{
					// No service was found
					this.LogMessage(NQLogType.Error, "Unknown service " + serviceInterface.Name + ".");
					throw new NQServiceLoadingException(NQErrorReason.UnknownService, "Unknown service " + serviceInterface.Name + ".");
				}
			}
		}

		public Type GetSubstitutingService(Type serviceInterface)
		{
			// Search recursively for the actual service that is substituting the given one
			if (_substitutions.ContainsKey(serviceInterface))
			{
				Type substitutor = _substitutions[serviceInterface];
				Type substitutor2 = this.GetSubstitutingService(substitutor);
				if (substitutor2 != null)
				{
					return substitutor2;
				}
				else
				{
					return substitutor;
				}
			}
			else
			{
				return null;
			}
		}

		public void InvokeService(object serviceObject)
		{
			if (serviceObject is INQInvokableService)
			{
				// Since the service implements INQInvokableService, call the method early-bound
				((INQInvokableService) serviceObject).InvokeService();
			}
			else
			{
				// Use reflection to call the method
				INQServiceInfo servInfo = this.GetServiceInfoFromObject(serviceObject);
				if (servInfo != null)
				{
					if (servInfo.InvokeMethod != null)
					{
						servInfo.InvokeMethod.Invoke(serviceObject, null);
					}
				}
			}
		}

		public void QuitService(object serviceObject)
		{
			if (serviceObject is INQInvokableService)
			{
				// Since the service implements INQInvokableService, call the method early-bound
				((INQInvokableService) serviceObject).QuitService();
			}
			else
			{
				// Use reflection to call the method
				INQServiceInfo servInfo = this.GetServiceInfoFromObject(serviceObject);
				if (servInfo != null)
				{
					if (servInfo.QuitMethod != null)
					{
						servInfo.QuitMethod.Invoke(serviceObject, null);
					}
				}
			}
		}

		public string ComponentPath
		{
			get
			{
				return _componentPath;
			}
		}

		public NQHostMode HostMode
		{
			get
			{
				lock (_syncObject)
				{
					return _hostMode;
				}
			}
		}

		public string GetAssemblyVersionString(Assembly assembly, string productName)
		{
			try
			{
				AssemblyDescriptionAttribute adesc = (AssemblyDescriptionAttribute) Attribute.GetCustomAttribute(assembly, typeof(AssemblyDescriptionAttribute));
				AssemblyName an = assembly.GetName();
				Version version = an.Version;
				return String.Format("{0} {5} (Build: {1}.{2}.{3}.{4})", productName, version.Major, version.Minor, version.Build, version.Revision, adesc.Description.Substring(1));
			}
			catch (Exception)
			{
				// Einfach Produktnamen zurückgeben
				return productName;
			}
		}

		public string GetAssemblyCopyright(Assembly assembly)
		{
			try
			{
				AssemblyCopyrightAttribute acopy = (AssemblyCopyrightAttribute) Attribute.GetCustomAttribute(assembly, typeof(AssemblyCopyrightAttribute));
				return acopy.Copyright + ". All rights reserved.";
			}
			catch (Exception)
			{
				return String.Empty;
			}
		}

		public void ExitApplication()
		{
			if (this.OnAppExitRequested())
			{
				// Exit the application
				Environment.Exit(0);
			}
		}

		public object SyncObject
		{
			get
			{
				return _syncObject;
			}
		}

		public void InvokeSyncMethod<P>(NQSynchronizedProcDelegate<P> method, P param)
		{
			if ((_hostMode == NQHostMode.GUI) && (_guiSyncObject != null))
			{
				// We need to invoke the method in the context of the GUI thread
				if (_guiSyncObject.InvokeRequired)
				{
					_guiSyncObject.Invoke(method, param);
				}
				else
				{
					method(param);
				}
			}
			else
			{
				// Since there is no GUI, lock with internal synchronizer object
				lock (_syncObject)
				{
					method(param);
				}
			}
		}

		public R InvokeSyncMethod<P, R>(NQSynchronizedDelegate<P, R> method, P param)
		{
			if ((_hostMode == NQHostMode.GUI) && (_guiSyncObject != null))
			{
				// We need to invoke the method in the context of the GUI thread
				if (_guiSyncObject.InvokeRequired)
				{
					return (R) _guiSyncObject.Invoke(method, param);
				}
				else
				{
					return method(param);
				}
			}
			else
			{
				// Since there is no GUI, lock with internal synchronizer object
				lock (_syncObject)
				{
					return method(param);
				}
			}
		}

		public void LogMessage(NQLogType logType, string logMessage, params object[] args)
		{
			this.LogMessage(null, logType, null, logMessage, args);
		}

		public void LogMessage(NQLogType logType, Exception logException)
		{
			this.LogMessage(null, logType, logException, null);
		}

		public void LogMessage(object fromService, NQLogType logType, Exception logException)
		{
			this.LogMessage(fromService, logType, logException, null);
		}

		public void LogMessage(object fromService, NQLogType logType, string logMessage, params object[] args)
		{
			this.LogMessage(fromService, logType, null, logMessage, args);
		}

		public void LogMessage(object fromService, NQLogType logType, Exception logException, string logMessage, params object[] args)
		{
			if (_noLog)
			{
				return;
			}

			DateTime timestamp = DateTime.Now;
			bool proceedLogging = false;
			bool proceedLoggingToFile = false;
			string logSource = null;
			string formattedMessage = null;
			if (logException != null)
			{
				// Add information about the given exception in the log
				string exceptionLog = logException.GetType().ToString() + ": " + logException.Message;

				// Check if command-line parameter "-ls" is set, write the stack trace if true.
				if (this.GetCmdLineParam("ls") != null)
				{
					exceptionLog += Environment.NewLine + logException.StackTrace;
				}

				if (String.IsNullOrEmpty(logMessage))
				{
					// No message, log only exception data
					formattedMessage = exceptionLog;
				}
				else
				{
					// Combine log message and exception data
					formattedMessage = String.Format("{0}: {1}", String.Format(logMessage, args), exceptionLog);
				}
			}
			else
			{
				// Log only the given message
				formattedMessage = String.Format(logMessage, args);
			}

			// Check if the log event should be processed
			switch (logType)
			{
				case NQLogType.Debug:
					proceedLogging = ((_logTypesEvents & NQLogType.Debug) == NQLogType.Debug);
					proceedLoggingToFile = ((_logTypesToFile & NQLogType.Debug) == NQLogType.Debug);
					break;

				case NQLogType.Error:
					proceedLogging = ((_logTypesEvents & NQLogType.Error) == NQLogType.Error);
					proceedLoggingToFile = ((_logTypesToFile & NQLogType.Error) == NQLogType.Error);
					break;

				case NQLogType.Warning:
					proceedLogging = ((_logTypesEvents & NQLogType.Warning) == NQLogType.Warning);
					proceedLoggingToFile = ((_logTypesToFile & NQLogType.Warning) == NQLogType.Warning);
					break;

				case NQLogType.Info:
					proceedLogging = ((_logTypesEvents & NQLogType.Info) == NQLogType.Info);
					proceedLoggingToFile = ((_logTypesToFile & NQLogType.Info) == NQLogType.Info);
					break;
			}

			if (proceedLoggingToFile)
			{
				// Set string representation of the level
				string typetext = String.Empty;
				switch (logType)
				{
					case NQLogType.Debug:
						typetext = "DEBUG";
						break;

					case NQLogType.Error:
						typetext = "ERROR";
						break;

					case NQLogType.Warning:
						typetext = "WARN";
						break;

					case NQLogType.Info:
						typetext = "INFO";
						break;
				}

				// Set the initiator's name
				if (fromService != null)
				{
					INQServiceInfo servinfo = this.GetServiceInfoFromObject(fromService);
					if (servinfo != null)
					{
						logSource = servinfo.ServiceInterface.Name;
					}
				}
				logSource = logSource ?? "<Base code>";

				// Write log message
				if (_logWriter != null)
				{
					_logWriter.WriteLine("[{0:G}.{0:fff}] [{1}] {2}: {3}", timestamp, logSource, typetext, formattedMessage);
					_logWriter.Flush();
				}
			}

			if (proceedLogging)
			{
				if (_logMessageCache != null)
				{
					// The log message cache is active, so cache the message
					_logMessageCache.CacheMessage(timestamp, fromService, logType, formattedMessage);
				}
				else
				{
					// No message cache: Fire the log event directly - and "thread-safely"
					this.InvokeSyncMethod<NQLogEventArgs, bool>(this.OnMessageLogged,
						new NQLogEventArgs(timestamp, fromService, logType, formattedMessage));
				}
			}
		}

		public string LogFile
		{
			get
			{
				return this._logFile;
			}
		}

		public bool IsActive
		{
			get
			{
				return _isActive;
			}
		}

		public NQLogType LogEventFilter
		{
			get
			{
				return _logTypesEvents;
			}
			set
			{
				_logTypesEvents = value;
			}
		}

		public NQLogType LogFileFilter
		{
			get
			{
				return _logTypesToFile;
			}
			set
			{
				_logTypesToFile = value;
			}
		}

		#endregion

		#region Private Methoden

		protected void OnServiceCreated(object service)
		{
			if (this.ServiceCreated != null)
			{
				ServiceCreated(this, new NQServiceCreatedEventArgs(service));
			}
		}

		protected void OnServiceDestroyed(Type serviceInterface)
		{
			if (this.ServiceDestroyed != null)
			{
				ServiceDestroyed(this, new NQServiceDestroyedEventArgs(serviceInterface));
			}
		}

		protected bool OnAppExitRequested()
		{
			if (this.AppExitRequested != null)
			{
				NQCancelEventArgs eventArgs = new NQCancelEventArgs();
				this.AppExitRequested(this, eventArgs);
				return eventArgs.Cancel;
			}
			else
			{
				return false;
			}
		}

		private bool CheckCategory(NQHostMode cat)
		{
			// Check if the service category allows the service to be loaded
			if (cat == NQHostMode.General)
			{
				return true;
			}
			return (_hostMode == cat);
		}

		internal bool OnMessageLogged(NQLogEventArgs e)
		{
			// Note: This method is internal to be accessible from LogMessageCache class
			if (this.MessageLogged != null)
			{
				try
				{
					this.MessageLogged(this, e);
				}
				catch (Exception ex)
				{
					// Something has gone wrong in one of the message handlers
					this.LogMessage(null, NQLogType.Error, ex, "OnMessageLogged");
				}
			}

			return true;
		}

		private string GetMD5Hash(string source)
		{
			MD5 md5dir = MD5.Create();
			byte[] sourcebytes = Encoding.UTF8.GetBytes(source);
			byte[] hashbytes = md5dir.ComputeHash(sourcebytes);
			StringBuilder result = new StringBuilder();
			for (int i = 0; i < hashbytes.Length; i++)
			{
				result.Append(hashbytes[i].ToString("x2"));
			}

			return result.ToString();
		}

		private T CreateServiceInstance<T>(Type serviceInterface, object[] constructorParams, Type[] cctorParamTypes, bool singleInstance)
		{
			Type realserv;

			// Check if the service has been substituted
			realserv = this.GetSubstitutingService(serviceInterface);
			if (realserv == null)
			{
				realserv = serviceInterface;
			}
			else if ((realserv != null) && singleInstance)
			{
				// We won't create the instance of a substituted single-instance service -> return null
				return default(T);
			}

			// Return service object
			return this.CreateServiceInstanceDirect<T>(realserv, constructorParams, cctorParamTypes, singleInstance);
		}

		private T CreateServiceInstanceDirect<T>(Type serviceInterface, object[] constructorParams, Type[] cctorParamTypes, bool singleInstance)
		{
			T newserv = default(T);

			// Return service instance without considering substitutes
			if (_serviceInfos.ContainsKey(serviceInterface))
			{
				// We should exit, when we try to create a single-instance service that already exists
				if (singleInstance)
				{
					if (_serviceSinglesList.ContainsKey(serviceInterface))
					{
						this.LogMessage(NQLogType.Error, "Tried to multiply load " + serviceInterface.Name + ".");
						throw new NQServiceLoadingException(NQErrorReason.ServiceMultiplyLoaded,
							"Tried to multiply load " + serviceInterface.Name + ".");
					}

					_singletonCtorLock = true;
				}

				try
				{
					INQServiceInfo servInfo = _serviceInfos[serviceInterface];
					INQServiceInstanceCreator serviceInstanceCreator = _serviceCreators[serviceInterface];
					newserv = serviceInstanceCreator.CreateInstance<T>(cctorParamTypes, constructorParams);

					// Some special work for single-instance services
					if (singleInstance)
					{
						_serviceSinglesList.Add(serviceInterface, newserv);

						// Initialize the service type
						_loadedSingleton = null;
						_singletonCtorLock = false;
					}

					// Fire ServiceCreated event
					this.OnServiceCreated(newserv);

					return newserv;
				}
				catch (TargetInvocationException ex)
				{
					if (ex.InnerException != null)
					{
						// We need the inner exception, rethrow it
						throw ex.InnerException;
					}
					else
					{
						// There is no inner exception, rethrow TargetInvocationException
						throw ex;
					}
				}
				finally
				{
					if (singleInstance)
					{
						_singletonCtorLock = false;
					}
				}
			}
			else
			{
				this.LogMessage(NQLogType.Error, "Unknown service " + serviceInterface.Name + ".");
				throw new NQServiceLoadingException(NQErrorReason.UnknownService, "Unknown service " + serviceInterface.Name + ".");
			}
		}

		#endregion

	}

}
